/*
 * PBBbotCodes.java
 *
 * Created on July 24, 2007, 9:46 AM
 * Nice little class to parse out Bot codes from a string and return back the values.
 *
 * Copyright 2007-2008 Jon W Huss III

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 *
 */

package pbb_template_updater;

import java.util.*;

/**
 *
 * @author Jon Huss
 */
public class PbbPageExaminer {
    
   
    //Default Behavior
    private boolean noBots = false;
    //boolean upPage = false;
    //boolean manInspect = true;
    //boolean upBox = false;
    //boolean upSum = false;
    private boolean badFormat = false; //Used to report a fatal error.
    //boolean upCite = false;
    
    //boolean warning = false; //Used to report non-fatal errors
    
    //String fatalError = ""; //Just keep adding all the errors on in sequence.
    //String warningError = "";
    //Behavior is to give the index of the start of the comment and the index of the end of the comment
    // surrounding the BOT command.
    
    //int boxStartIndex = -1;
    //int boxEndIndex = -1;
    //int sumStartIndex = -1;
    //int sumEndIndex = -1;
    
    //this ensures that the box title and some key pointing towards it never clash with box data in the
    //hashtable.
    public static final String BOX_TITLE_KEY = "|||TITLE_KEY|||_1dggusy_35b338fbfb__|||";
    public static final String BOX_ERROR_KEY = "|||ERROR_KEY|||_HUEUIQnnfuue_773jjf983___|||";
    
    public static final String NO_ERRORS = "0";
    public static final String YES_ERRORS = "1";
    
    public enum templates {
        PBB_SUMMARY{
            String text() {return "PBB_Summary";}
            private int begin = 0;
            private int end = 0;
            
            void setBegin(int x) { begin = x; };
            int getBegin() { return begin; };
            void setEnd(int x) { end = x; };
            int getEnd() {return end; };
            
        },
        PBB_CONTROLS{
            String text() {return "PBB_Controls";}
            private int begin = 0;
            private int end = 0;
            
            void setBegin(int x) { begin = x; };
            int getBegin() { return begin; };
            void setEnd(int x) { end = x; };
            int getEnd() {return end; };
        },
        PBB_FURTHER_READING{
            String text() {return "PBB_Further_reading";}
            private int begin = 0;
            private int end = 0;
            
            void setBegin(int x) { begin = x; };
            int getBegin() { return begin; };
            void setEnd(int x) { end = x; };
            int getEnd() {return end; };
        },
        GNF_PROTEIN_BOX{
            String text() {return "GNF_Protein_box";}
            private int begin = 0;
            private int end = 0;
            
            void setBegin(int x) { begin = x; };
            int getBegin() { return begin; };
            void setEnd(int x) { end = x; };
            int getEnd() {return end; };
            
        };
        abstract String text();
        
        abstract void setBegin(int x); 
        abstract int getBegin(); 
        abstract void setEnd(int x);
        abstract int getEnd();
    }
    
    String pageData;
    /** Creates a new instance of PBBbotCodes */
    public PbbPageExaminer(String data) {
        
        if (data == null){
            data = "";
        }
        
        pageData = data;
        //lets make everything uppercase so we don't have to worry about'
        
        String dataU = data.toUpperCase();
        //See if there is "No Bot Edits" or "{{nobots}}"
        noBots = findNoBots(dataU);
        
        String result = this.findTemplate(data,templates.GNF_PROTEIN_BOX);
            if (result.compareTo("") == 0) {
                //we didn't find a template, but we were supposed to.  Need to raise a warning...'
                this.badFormat = true;
                //this.warning = true;
                //this.warningError += "Could not find protein box. ";
                
            }
        
//        //Now look for the control template..
//        
//        Hashtable<String,String> bControls = this.parseBox(this.findTemplate(data,templates.PBB_CONTROLS));
//        
//        //Set the update booleans based on this information.
//        if (bControls.containsKey("update_page")){
//            String result = bControls.get("update_page");
//            if (result.toLowerCase().compareTo("yes") == 0 ){
//                this.upPage = true;
//            } 
//            else {
//                this.upPage = false;
//            }
//                
//        } else {
//            this.badFormat = true;
//        }
//        
//        if (bControls.containsKey("require_manual_inspection")){
//            String result = bControls.get("require_manual_inspection");
//            if (result.toLowerCase().compareTo("no") == 0 ){
//                this.manInspect = false;
//            }else {
//                this.manInspect = true;
//            }
//                
//        } else {
//            this.badFormat = true;
//        }
//        
//        if (bControls.containsKey("update_protein_box")){
//            String result = bControls.get("update_protein_box");
//            if (result.toLowerCase().compareTo("yes") == 0 ){
//                this.upBox = true;
//            }else {
//                this.upBox = false;
//            }
//                
//        } else {
//            this.badFormat = true;
//        }
//        
//        if (bControls.containsKey("update_summary")){
//            String result = bControls.get("update_summary");
//            if (result.toLowerCase().compareTo("yes") == 0 ){
//                this.upSum = true;
//            }else {
//                this.upSum = false;
//            }
//                
//        } else {
//            this.badFormat = true;
//        }
//        
//        if (bControls.containsKey("update_citations")){
//            String result = bControls.get("update_citations");
//            if (result.toLowerCase().compareTo("yes") == 0 ){
//                this.upCite = true;
//            }else {
//                this.upCite = false;
//            }
//                
//        } else {
//            this.badFormat = true;
//        }
//        //No need to search for bot Tags any more.. we have it all in one box.  
//        
//        //we are going to set a policy - if we are to update it, then and only then will we search for it.
//        
//        if (upBox) {
//            //look for the protein box...
//            String result = this.findTemplate(data,templates.GNF_PROTEIN_BOX);
//            if (result.compareTo("") == 0) {
//                //we didn't find a template, but we were supposed to.  Need to raise a warning...'
//                upBox = false;
//                this.warning = true;
//                this.warningError += "Could not find protein box. ";
//                //this.badFormat = true;
//            }
//        }
//        
//        if (upSum) {
//            String result = this.findTemplate(data,templates.PBB_SUMMARY);
//            if (result.compareTo("") == 0) {
//                //we didn't find a template, but we were supposed to.  Need to raise a Warning...'
//                upSum = false;
//                this.warning = true;
//                this.warningError += "Could not find summary box. ";
//                //this.badFormat = true;
//            }
//        }
//        
//        if (upCite) {
//            
//            String result = this.findTemplate(data,templates.PBB_FURTHER_READING);
//            if (result.compareTo("") == 0) {
//                //we didn't find a template, but we were supposed to.  Need to raise a bad format error.'
//                upCite = false;
//                this.warning = true;
//                this.warningError += "Could not find citation box. ";
//                //this.badFormat = true;
//            }
//        }
        
        //At this point we should have parsed all the boxes and we just need to return to main processing..
        return;
        
        
//        Vector<Integer> commandIndex = new Vector<Integer>();
//        int index1 = 0;
//        int index2 = 0;
//        //these booleans make sure that the formatting is good.
//        boolean foundManIns = false;
//        boolean foundBox = false;
//        boolean foundSumS = false;
//        boolean foundSumE = false;
//        
//        while ((index1 = data.indexOf("BOT:",index2)) != -1){
//            //Ok, possibly found a bot code, now we need to make sure it is within a comment..
//            //so now we try and find the surrounding comment. fist we look backward.
//            int comIndexF; //Front of Comment
//            int comIndexB; //Back of Comment
//            int tmpIndex;
//            comIndexF = data.lastIndexOf("<!--",index1);
//            //while (tmpIndex < index1 && tmpIndex != -1) {
//            //    comIndexF = tmpIndex;
//            //    tmpIndex = data.indexOf("<!--",comIndexF + 1);
//            //
//            //}
//            //Should now have the front of the comment...
//            if (comIndexF > index1 || comIndexF < index2) {
//                //Ok we have a problem - that Bot command does not have an opening comment.  We are going
//                //skip it. It could be part of the normal text...
//                index2 = index1 + 4;
//                continue;
//            }
//            //now need to find the back end of the comment.
//            comIndexB = data.indexOf("-->",index1);
//            if (comIndexB == -1) {
//                //Didn't find a closing bracket to match the opening bracket.  Going to raise the
//                //bad formatting flag and quit the search (as there are no more 'real' comments).
//                badFormat = true;
//                break;
//            }
//            
//            //If we get here we have found the opening and closing indexes of the comment
//            // now we just need to find out if we have a valid command...
//            //first a little bookkeeping
//            
//            
//            index2 = comIndexB;
//            String temp = data.substring(comIndexF,comIndexB);
//            if (temp.matches("(?si).*<!--\\s*BOT:\\s*MANUAL[\\s|_]*INSPECTION[\\s|_]*REQUIRED\\s*=\\s*NO.*")){
//                //requires a manual inspection
//                foundManIns = true;
//                manInspect = false;
//            }else if (temp.matches("(?si).*<!--\\s*BOT:\\s*MANUAL[\\s|_]*INSPECTION[\\s|_]*REQUIRED\\s*=\\s*YES.*")){
//                //requires a manual inspection
//                foundManIns = true;
//                manInspect = true;
//            }else if (temp.matches("(?si).*<!--\\s*BOT:\\s*PROTEIN[\\s|_]*BOX[\\s|_]*UPDATE\\s*=\\s*YES.*")){
//                foundBox = true;
//                upBox = true;
//                //Set the box bounds to right after the comment.
//                boxStartIndex = comIndexB + 3;
//                boxEndIndex = comIndexB + 3;
//                this.findGNFBox(data);
//            }else if (temp.matches("(?si).*<!--\\s*BOT:\\s*PROTEIN[\\s|_]*BOX[\\s|_]*UPDATE\\s*=\\s*NO.*")){
//                foundBox = true;
//                upBox = false;
//                //Set the box bounds to right after the comment.
//                boxStartIndex = comIndexB + 3;
//                boxEndIndex = comIndexB + 3;
//                this.findGNFBox(data);
//            }else if (temp.matches("(?si).*<!--\\s*BOT:\\s*SUMMARY[\\s|_]*BEGIN[\\s|_]*UPDATE\\s*=\\s*NO.*")){
//                foundSumS = true;
//                upSum = false;
//                //Set the Summary start to right after the comment.
//                sumStartIndex = comIndexB + 3;
//            }else if (temp.matches("(?si).*<!--\\s*BOT:\\s*SUMMARY[\\s|_]*BEGIN[\\s|_]*UPDATE\\s*=\\s*YES.*")){
//                foundSumS = true;
//                upSum = true;
//                //Set the Summary start to right after the comment.
//                sumStartIndex = comIndexB + 3;
//            }else if (temp.matches("(?si).*<!--\\s*BOT:\\s*SUMMARY[\\s|_]*END[\\s|_]*.*")){
//                foundSumE = true;
//                //Set the Summary end to just before the comment.
//                sumEndIndex = comIndexF;
//            }else {
//                //Got an unknown command - need to fix that.
//                badFormat = true;
//            }
//            
//        }
//        if (!(foundManIns && foundSumE && foundSumS && foundBox)) {
//            //didn't find all the fields.  need to throw up the bad formatting flag.
//            badFormat = true;
//        }
//        if (sumEndIndex < sumStartIndex){
//            badFormat = true;
//        }
//        if (boxEndIndex < boxStartIndex) {
//            badFormat = true;
//        }
//        
        
        
    }
    
    public int getBoxBegin(templates t){
        return t.getBegin();
    }
    
    public int getBoxEnd(templates t){
        return t.getEnd();
    }
    
    public Hashtable<String,String> getBoxFields(templates t){
        return this.parseBox(this.findTemplate(pageData,t));
    }
    
    
    
    private Hashtable<String,String> parseBox(String box){
        //requires that the box include {{ and }} at the start and end.
        //Also, this will only parse for the box outside.
        
        int braceCount = 0;
        int charIndex = 0;
        Hashtable<String,String> data = new Hashtable<String,String>();
        data.put(this.BOX_ERROR_KEY,this.NO_ERRORS);
        
        int boxStart = box.indexOf("{{");
        if (boxStart != 0){
            data.put(this.BOX_ERROR_KEY, this.YES_ERRORS);
            return data; //no box found.
        }
        braceCount = 1; //1 pair of braces so far..
        int titleEnd = box.indexOf("|",boxStart+2);
        int boxEnd = box.lastIndexOf("}}");
        if (boxEnd+2 != box.length()){
            //this box has no end - error and leave.
            data.put(this.BOX_ERROR_KEY, this.YES_ERRORS);
            return data;
        }
        //error catching
        if (titleEnd == -1){
            //nothing in the box. Look for the end brace.
            titleEnd = box.indexOf("}}",boxStart+2);
            if (titleEnd == -1) {
                //this box has no end - need to raise an error.
                data.put(this.BOX_ERROR_KEY,this.YES_ERRORS);
                return data;
            } else {
                data.put(this.BOX_TITLE_KEY,box.substring(boxStart+2,titleEnd-1).trim());
                return data;
            }
        }
        data.put(this.BOX_TITLE_KEY,box.substring(boxStart+2,titleEnd-1).trim());
        //Now have the title.
        
        int nextKeyS = titleEnd+1;
        int nextKeyE = titleEnd+1;
        
        int nextValS = titleEnd+1;
        int nextValE = titleEnd+1;
        
        boolean inData = false;
        boolean inKey = true;
        
        
        for (int i = titleEnd+1; i < boxEnd; i++){
            String tmp = box.substring(i,i+1);
            String tmp2 = box.substring(i,i+2);
            if (tmp2.compareTo("{{") == 0 || tmp2.compareTo("[[") == 0 || tmp2.compareTo("{|") == 0 ){
                //We have opened another brace - we can only gather an attribute if the brace count is
                //equal to 2.
                braceCount++;
                i++; //skip an i
                continue;
            }
            
            if (tmp2.compareTo("}}") == 0 || tmp2.compareTo("]]") == 0 || tmp2.compareTo("|}") == 0){
                //We have closed another brace - we can only gather an attribute if the brace count is
                //equal to 2.
                braceCount--;
                i++; //skip an i
                continue;
            }
            
            if (braceCount < 1){
                //Error - box seemed to end early.
                data.put(this.BOX_ERROR_KEY,this.YES_ERRORS);
                return data;
            }
            //we only deal with the outermost box, so we'll have to be in BraceCount 1 to gather keys or vals..'
            if (braceCount == 1){
                //we have two options - we can either be in a key, or in a value.
                //we could also be ending a key or ending a value.
                if (inKey){
                    //So we are gathering up a key
                    if (tmp.compareTo("=") == 0){
                        //we have found the end of the key.
                        nextKeyE = i-1;
                        nextValS = i+1;
                        inKey = false;
                        inData = true;
                        continue;
                    }
                    //error values:
                    if (tmp.compareTo("|") == 0) {
                        //error
                        data.put(this.BOX_ERROR_KEY,this.YES_ERRORS);
                        return data;
                    }
                    
                }
                if (inData) {
                    //So we are gathering up a value for a key.
                    if (tmp.compareTo("|") == 0){
                        //so we are not in any subtemplate, which means that we just ended the data.
                        nextValE = i-1;
                        
                        data.put(box.substring(nextKeyS,nextKeyE).trim(),box.substring(nextValS,nextValE).trim());
                        
                        inKey = true;
                        inData = false;
                        nextKeyS = i+1;
                        nextKeyE = i+1;
                        nextValS = i+1;
                        nextValE = i+1;
                        
                    } else if (i == boxEnd-1){
                        //last data value.
                        nextValE = i;
                        
                        data.put(box.substring(nextKeyS,nextKeyE).trim(),box.substring(nextValS,nextValE).trim());
                        
                        inKey = true;
                        inData = false;
                        nextKeyS = i+1;
                        nextKeyE = i+1;
                        nextValS = i+1;
                        nextValE = i+1;
                        
                    }
                    //pretty much any other value is ok.
                }
            }
            
            
        }
        
        return data;
        
    }
    
    
    /* This might need some more work in the future -- I am worried about successive closing
     * braces ( {{{ or }}} as thos will count as two instead of one and a half..
     */
    private String findTemplate(String text, templates title){
        
        //Wikipedia is caps sensitive, so we should probably be too..
        int indexS;
        int count = 0;
        int indexE = -1;
        String data = "";
        if ((indexS = text.indexOf("{{"+title.text())) != -1){
            //indexS now has the start location of the template.
            count = 1;
            for (int i = indexS + 2; i < text.length() - 2; i++ ){
                String tmp = text.substring(i,i+2);
                if (tmp.compareTo("{{") == 0){
                    count++;
                }
                if (tmp.compareTo("}}") == 0){
                    count--;
                }
                if (count == 0){
                    //end of template.
                    indexE = i + 2;
                    break;
                }
            }
            if (indexE > indexS){
                data = text.substring(indexS,indexE);
                title.setBegin(indexS);
                title.setEnd(indexE);
                return data;
            }
            
            
        }
        
        return data;
        
    }
    
//    private void findGNFBox(String text) {
//        int indexS;
//        int count = 0;
//        
//        if((indexS = text.indexOf("GNF_PROTEIN_BOX")) != -1){
//            //Not interested in changing the set indexes if the box is not actually there
//            //(cause we'll just insert a new box)
//            indexS = text.lastIndexOf("{{",indexS);
//            //find the first set of Brackets.
//            if (indexS == -1){
//                //got a problem with the box.
//                badFormat = true;
//                return;
//            }
//            boxStartIndex = indexS; //Set the value globally.
//            //Now indexS points to the front of the box.  We need to find the LAST set of '}}' in the Box.
//            count = 1; //We already found one opening.
//            int index1 = indexS + 2;
//            String tmp = "";
//            while (count > 0 && index1 < text.length() - 2 ) {
//                tmp = text.substring(index1,index1+2);
//                if (tmp.compareTo("{{") == 0){
//                    //opened a brace, increment the count
//                    count++;
//                }
//                if (tmp.compareTo("}}") == 0){
//                    //closed a brace, decrement the count
//                    count--;
//                }
//                index1++;
//            }
//            if (count == 0){
//                //found the end of the box! ya!
//                boxEndIndex = index1 + 2; //that should point us right on the last brace.
//                
//            }else {
//                badFormat = true; //Something went wrong.
//            }
//            
//        }else {
//            badFormat = true;
//        }
//        
//    }
//    
    private boolean findNoBots(String data) {
        
        //Look for something resembling <!-- NO BOT EDITS --> or {{nobots}}
        if (data.matches("(?si).*<!--\\s*NO[\\s|_]*BOT[\\s|_]*EDITS\\s*-->.*") || data.indexOf("{{NOBOTS}}") != -1){
            return true;
        }
        return false;
    }

    public boolean isBadFormat() {
        return badFormat;
    }
//    
//    public int getBoxEndIndex() {
//        return boxEndIndex;
//    }
//    
//    public int getBoxStartIndex() {
//        return boxStartIndex;
//    }
//    
//    public int getSumEndIndex() {
//        return sumEndIndex;
//    }
//    
//    public int getSumStartIndex() {
//        return sumStartIndex;
//    }
    
//    public boolean ReqsManInspect() {
//        return manInspect;
//    }
    
    public boolean isNoBots() {
        return noBots;
    }
    
//    public boolean UpdateBox() {
//        return upBox;
//    }
//    
//    public boolean UpdateSum() {
//        return upSum;
//    }
//    
//    public boolean UpdateCite() {
//        return upCite;
//    }
    
}
